/**
 * @(#)AS400EntityManager.java	0.1 Nov 6, 2007
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.wideskill.rpg4ejb3;

import static com.wideskill.rpg4ejb3.type.annotation.AS400Column.Usage.INPUT;
import static com.wideskill.rpg4ejb3.type.annotation.AS400Column.Usage.OUTPUT;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Iterator;

import javax.annotation.Resource;
import javax.sql.DataSource;

import com.ibm.as400.access.AS400;
import com.ibm.as400.access.AS400JDBCConnection;
import com.ibm.as400.access.AS400JDBCConnectionHandle;
import com.ibm.as400.data.PcmlException;
import com.ibm.as400.data.ProgramCallDocument;
import com.wideskill.rpg4ejb3.type.AbstractType;
import com.wideskill.rpg4ejb3.type.ProcedureTypeInfo;
import com.wideskill.rpg4ejb3.type.annotation.AS400Column;
import com.wideskill.rpg4ejb3.type.annotation.AS400ProcedureType;
import com.wideskill.rpg4ejb3.type.annotation.As400ColumnType;
import com.wideskill.rpg4ejb3.type.annotation.EntityAS400RPG;

/**
 * @author: Luigi Scarpato <luigi.scarpato at wideskill.com>
 * 
 */
public class AS400EntityManager<E> {

	@Resource
	private DataSource dataSource;

	private AS400JDBCConnection as400Connection;
	private ProcedureTypeInfo<EntityAS400RPG, AS400Column> beanInfo = null;

	private String procedureName;

	public AS400EntityManager(DataSource dataSource){
		if (dataSource == null) {
			throw new NullPointerException();
		}
		this.dataSource = dataSource;
	}


	/**
	 * @param entity
	 * @return
	 * @throws Exception
	 */
	public E call(E entity) throws RPGCallException {

		inizialize(entity);
		
		AS400 sys = null;
		Connection connection = null;
		try {
			connection = dataSource.getConnection();

			sys = getAs400System(connection);
			
			//  To run the test, you need to create META-INF/api.pcml file in the classpath
			//  Refer to PCML syntax for a detailed description of the language. 
			//  http://publib.boulder.ibm.com/infocenter/iadthelp/v7r0/index.jsp?topic=/com.ibm.etools.iseries.toolbox.doc/pcmlproc.htm
			ProgramCallDocument pcml = new ProgramCallDocument(sys, "META-INF.api.pcml", 
					Thread.currentThread().getContextClassLoader());

			if (prepareCallDocument(pcml)) {
				try {
					boolean rc = pcml.callProgram(getProcedureName());
					if (rc) {
						postCallDocument(pcml, entity);
					}
				} catch (PcmlException e) {
					throw new SQLException(e.getMessage());
				}
			}
		} catch (SQLException e) {
			throw new RPGCallException(e);
		} catch (PcmlException e) {
			throw new RPGCallException(e);
		} finally {
			if (sys!=null)
				sys.disconnectService(AS400.COMMAND);
			
			if (connection != null)
				try {
					connection.close();
					connection = null;
				} catch (SQLException e) {
				}
		}

		return entity;
	}

	/**
	 * @param entity
	 */
	protected void inizialize(E entity) {
		beanInfo = new ProcedureTypeInfo<EntityAS400RPG, AS400Column>(entity);
	}
	
	/**
	 * @param connection
	 * @throws SQLException 
	 */
	protected AS400 getAs400System(Connection connection) throws SQLException {
		
		Object connectionHandle = connection;
		
		if(connectionHandle instanceof AS400JDBCConnectionHandle) {
		    // Get the AS400 connection from the handle, in order to get the AS400 object from it
		    AS400JDBCConnectionHandle as400ConnectionHandle = (AS400JDBCConnectionHandle)connectionHandle;
		    as400Connection = (AS400JDBCConnection)as400ConnectionHandle.getMetaData().getConnection();
			return as400Connection.getSystem();
		}
		return null;
	}

	/**
	 * 
	 */
	protected void cleanup(AS400 sys) {
		try {
			if (sys!=null)
				sys.disconnectService(AS400.COMMAND);
			
			if (as400Connection != null)
				as400Connection.close();

			as400Connection = null;
		} catch (SQLException sqle) {
			sqle.printStackTrace();
		}
	}

	/**
	 * @return
	 */
	protected String getProcedureName() {
		if (procedureName == null) {
			AS400ProcedureType procedureType = (AS400ProcedureType) beanInfo.getProcedureType();
			procedureName = procedureType.getName();
		}
		return procedureName;
	}

	/**
	 * @param pcml
	 * @return
	 * @throws SQLException
	 */
	protected boolean prepareCallDocument(ProgramCallDocument pcml)
			throws SQLException {

		try {
			for (Iterator<? extends AbstractType<AS400Column>> iterator = beanInfo.getColumnTypeIterator(); iterator.hasNext();) {
				As400ColumnType column = (As400ColumnType) iterator.next();

				if (column.getUsage() == INPUT) {
					int len = pcml.getOutputsize(getProcedureName() + "." + column.getName());
					Object v = column.getNormalize().valorize(column.getValue()!=null ? String.valueOf(column.getValue()) : "", len);
					pcml.setValue(getProcedureName() + "." + column.getName(), v);
				}
			}
			return true;
		} catch (PcmlException e) {
			e.printStackTrace();
		}
		return false;
	}

	/**
	 * @param pcml
	 * @param entity 
	 * @return
	 * @throws SQLException
	 */
	protected boolean postCallDocument(ProgramCallDocument pcml, E entity)
			throws SQLException {
		try {
			for (Iterator<? extends AbstractType<AS400Column>> iterator = beanInfo.getColumnTypeIterator(); iterator.hasNext();) {
				As400ColumnType column = (As400ColumnType) iterator.next();

				if (column.getUsage() == OUTPUT) {

					Object v = pcml.getValue(getProcedureName() + "."
							+ column.getName());

					Field field = column.getField();
					try {
						field.set(entity, v);
					} catch (IllegalArgumentException e) {
						// ignore
					} catch (IllegalAccessException e) {
						// ignore
					}
				}
			}
			return true;
		} catch (PcmlException e) {
			// ignore
		}
		return false;
	}
}
